--__auto_local__start--
local string=string
local debug=debug
local string_format = string.format
--__auto_local__end--

traceback = {}

-- frame count exclude gettop
function traceback.gettop()
	local Max_Level = 58
	local level = 2 
	while true do
		if level > Max_Level then break end
		local info = debug.getinfo(level + 1, "n")
		if info == nil then break end
		level = level + 1
	end

	-- gettop
	level = level - 1
	return level
end

-- frame negative index
function traceback.getindex()
	local curframe = -traceback.gettop() -- curframe
	local retval = curframe + 1 -- caller	
	return retval 
end

function traceback.getinfo(level, what)
	if level < 0 then
		level = traceback.gettop() + 1 + level
	end
	return debug.getinfo(level, what)
end

function traceback.getsource(level)
	local info = traceback.getinfo(level, "Sl")
	if info == nil then return nil, nil, nil end
	local filename = info.source
	if string.find(filename, "@") == 1 then
		filename = string.sub(filename, 2)
	else
		filename = filename
	end
	return info.what, filename, info.currentline
end

-- 判断是否控制字符
local function iscntrl(ch)
	local b = string.byte(ch)
	return b >= 0x00 and b <= 0x1f
end

local function iscntrl_string(str)
	local count = string.len(str)
	local ch, by
	for i = 1, count do
		ch = string.sub(str, i, i)
		by = string.byte(ch)
		if by >= 0x00 and by <= 0x1f then
			return true
		end
	end
	return false
end

local MAX_STR_LEN = 200
function dump_locals(level, result)
	local info = debug.getinfo(level)
	if info == nil then return end

	local i = 1
	local values = {}

	if info.func then
		i = 1
		while true do 
			local name, value = debug.getupvalue(info.func, i)
			if not name then break end
			values[name] = value
			i = i + 1
		end
	end

	i = 1
	while true do
		local name, value = debug.getlocal(level, i)
		if not name then break end
		values[name] = value
		i = i + 1
	end

	local tp, str, show, kv
	local idx = #result
	for k, v in pairs(values) do
		tp = type(v)
		str = tostring(v)
		show = string.sub(str, 1, MAX_STR_LEN)
		if tp == "string" then
			if iscntrl_string(str) then
				show = "<binary data>"
			end
			kv = string_format("\t\t%s = <%s-%d> %s\n", k, tp, string.len(str), show)
		elseif tp == "table" then
			kv = string_format("\t\t%s = <%s-%d> %s\n", k, tp, table.size(v), show)
		else
			kv = string_format("\t\t%s = <%s> %s\n", k, tp, show)
		end
		idx = idx + 1
		result[idx] = kv
	end
end

local MAX_STACK_LEVEL = 50
function traceback.dumpstr(offset)
	local top = traceback.gettop()
	local curframe = (1 - top) + 1
	if offset and offset > 0 then
		curframe = curframe + offset
	end
	local result = { string_format("traceback: top=%d, curframe=%d\n", top, curframe) }
	local idx = 1
	local levelCnt = 0
	for frame = curframe, -1 do
		local level = (top + 1) + frame
		local info = debug.getinfo(level, "nfSlu")
		if info == nil then break end

		local i = frame - curframe + 1
		if info.what == "C" then
			if info.name ~= nil then
				idx = idx + 1
				result[idx] = string_format("%4d[C] : in %s\n", i, info.name)
			else
				idx = idx + 1
				result[idx] = string_format("%4d[C] :\n", i)
			end
		elseif info.name ~= nil then
			idx = idx + 1
			result[idx] = string_format("%4d %s:%d in %s\n", i, info.short_src, info.currentline, info.name)
		else
			idx = idx + 1
			result[idx] = string_format("%4d %s:%d\n", i, info.short_src, info.currentline)
		end
		dump_locals(level+1, result)
		idx = #result
		levelCnt = levelCnt + 1
		if levelCnt >= MAX_STACK_LEVEL then break end
	end
	return table.concat(result, "")
end

function traceback.dump(offset)
	print(traceback.dumpstr(offset))
end
function debug.printx(...)
    local info = debug.getinfo(2)
    if info then
        print(string_format("[file]=%s,[line]=%d:",info.source or "?",info.currentline or 0), ...)
    end
end

-- 打印函数的upval
function debug.show_upval(func)
	local info = debug.getinfo(func, "nfSlu")
	_INFO("ShowUpvalue: func=%s, upval=%s", tostring(func), info.nups)

	local i = 1
	while true do
		local n, v = debug.getupvalue(func, i)
		if not n then break end
		_INFO("[%d] %s = %s", i, n, tostring(v))
		i = i + 1
	end
end

function debug.excepthook(errmsg)
	xpcall(function() Error2ExceptHook(errmsg) end, print)
	return errmsg
end